package an_lexico;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Classe usada para criar o Analisador Lexico. Esta classe se encarrega de
 * ler os arquivos de configuração, inicializar o Analisador Lexico e retorna-lo
 * ao programa principal pronto para ser usado.
 *
 * @author Fernando
 * @author Pedro Sol
 */
public class AnLexicoFactory {

    /**
     * Maximum number of Token Types.
     */
    protected static final int Max_Type_Of_Tokens = 100;

    /**
     * AnLexicoFactory.
     */
    protected static AnLexicoFactory factory;

	/**
	 * Analisador Lexico.
     */
    protected int colLex,  linLex,  transTableLex[][],  initialStateLex;

	/**
	 * Tabela que indica como interpretar as colunas da tabela de transições
     * Ex: Se a coluna tiver um a-z, se a maquina de estados achar uma letra
     *     entre a e z, ele faz a transição indicada pelo estado para aquela
     *     coluna.
     */
    protected char InputMap[][];

    /**
     * Final states of the Analisador Lexico.
     */
    protected int[] finalStatesLex;

    /**
     * lastTokenTypeNumberLex.
     */
    protected int lastTokenTypeNumberLex;

    /**
     * ID of the Token Types.
     */
    protected String tokenTypeIDs[];

    /**
     * Reserved words of the grammar.
     */
    protected String[] reservedWords;

    /**
     * Configuration file from the Analisador Léxico.
     */
    protected String configFileLex = "./config/tt_lex.txt";

    /**
     * PrintStream.
     */
    protected PrintStream printTo;

    /**
     * AnLexicoFactory.
     */
    private AnLexicoFactory() {
        initialStateLex = 0;
        tokenTypeIDs = new String[Max_Type_Of_Tokens];
        printTo = new PrintStream(new OutputStream() {

            @Override
            public void write(int b) throws IOException {
                return;
            }
        });
    }

    /**
     * getFactory.
     *
     * @return factory
     */
    public static AnLexicoFactory getFactory() {
        if (factory == null) {
            factory = new AnLexicoFactory();
        }
        return factory;
    }

    /**
     * Set the configuration file from the Analisador Léxico.
     *
     * @param configFileLex Configuration file from the Analisador Léxico
     */
    public void setConfigFileLex(String configFileLex) {
        this.configFileLex = configFileLex;
    }

    /**
     * Create and configure an Analisador Léxico.
     *
     * @param sourceFile
     * @return a1 Analisador Léxico
     */
    public AnLexico createAnLexico(String sourceFile) {
        AnLexico al = new AnLexico();
        lastTokenTypeNumberLex = 1;

        ReadConfigLex();
        al.setCol(colLex);
        al.setLin(linLex);

        al.setInitialState(initialStateLex);
        al.setFinalState(finalStatesLex);
        tokenTypeIDs[0] = "Nada";
        al.setTokenTypeID(tokenTypeIDs);

        al.setInputMap(InputMap);
        al.setTransitions(transTableLex);

        al.setLastFinalStateTokenType(lastTokenTypeNumberLex);
        al.setReservedWords(reservedWords);

        File f = new File(sourceFile);
        InputStreamReader isr;

        try {
            FileInputStream fis = new FileInputStream(f);
            isr = new InputStreamReader(fis, "UTF8"); //arquivo fonte - a ser passado para o analizador léxico.

        } catch (UnsupportedEncodingException ex) {
            throw new Error("Erro na abertura do arquivo!! - Fonte - Encoding");
        } catch (FileNotFoundException fnf) {
            throw new Error("Erro na abertura do arquivo!! - Fonte nao encontrado: " + fnf.getMessage() + " " + sourceFile);
        }

        al.setSourceFile(isr);
        return al;
    }
    /**
     * Read the configuration file
     */
    private void ReadConfigLex() {

        DataInputStream dis = openConfigFile(configFileLex);

        try {
            getSizeOfTransitionTableLex(dis);
            getInputMapLex(dis);

            transTableLex = new int[colLex][linLex];
            finalStatesLex = new int[linLex];

            for (int i = 0; i < linLex; i++) {
                @SuppressWarnings("deprecation")
                StringTokenizer st = new StringTokenizer(dis.readLine());
                String tipo = st.nextToken();
                if (tipo.equals("f")) {
                    finalStatesLex[i] = lastTokenTypeNumberLex;
                    tokenTypeIDs[i] = st.nextToken();
                    printTo.print("tokenTypeID = " + tokenTypeIDs[i] + " lastTokenTypeNumberLex = " + lastTokenTypeNumberLex);
                    lastTokenTypeNumberLex++;
                } else {
                    finalStatesLex[i] = 0;
                }
                printTo.println(" StateLine = " + i);
                getTransitionLineLex(st, i);
            }
            getReservedWordsLex(dis);
        } catch (IOException ex) {
            Logger.getLogger(AnLexicoFactory.class.getName()).log(Level.SEVERE, null, ex);
            throw new Error(ex);
        }
    }

    /**
     * Extract reserved words from the configuration file.
     *
     * @param DataInputStream Configuration file
     */
    @SuppressWarnings("deprecation")
    private void getReservedWordsLex(DataInputStream dis) throws IOException {
        StringTokenizer st = new StringTokenizer(dis.readLine());
        String temp = st.nextToken();
        printTo.println("numReservedWords = " + temp);
        int numReservedWords = Integer.parseInt(temp);
        reservedWords = new String[numReservedWords];
        for (int i = 0; i < numReservedWords; i++) {
            st = new StringTokenizer(dis.readLine());
            reservedWords[i] = st.nextToken();
        }
    }

    /**
     * Open the configuration file.
     *
     * @param path File path
     * @return DataInputStream Léxico Configuration file
     */
    private DataInputStream openConfigFile(String path) {
        File f = new File(path);
        try {
            FileInputStream fis = new FileInputStream(f);
            DataInputStream dis = new DataInputStream(fis);
            return dis;
        } catch (FileNotFoundException fnf) {
            throw new Error("Erro na abertura do arquivo!! - ConfigFile.");
        }
    }

    /**
     * getInputMapLex.
     *
     * @param DataInputStream Léxico Configuration file
     */
    private void getInputMapLex(DataInputStream dis) throws IOException {
        InputMap = new char[colLex][2];
        @SuppressWarnings("deprecation")
        StringTokenizer st = new StringTokenizer(dis.readLine());
        st.nextToken(); //Joga fora o primeiro token pq nao indica nenhum tipo de input

        for (int i = 0; i < colLex; i++) {
            String input = st.nextToken();
            if (input.length() > 1) {
                if (input.equals("spc")) {
                    InputMap[i][0] = ' ';
                    InputMap[i][1] = ' ';
                } else {
                    InputMap[i][0] = input.charAt(0);
                    InputMap[i][1] = input.charAt(2);
                }
            } else {
                InputMap[i][0] = input.charAt(0);
                InputMap[i][1] = InputMap[i][0];
            }
        }

    }

    /**
     * getTransitionLineLex.
     *
     * @param StringTokenizer
     */
    private void getTransitionLineLex(StringTokenizer st, int i) throws NumberFormatException, Error {

        if (st.countTokens() > colLex) {
            Error ex = new Error("Numero de colunas errado no arquivo de configuração! esperado: " + colLex + " encontrado: " + st.countTokens());
            Logger.getLogger(AnLexicoFactory.class.getName()).log(Level.WARNING, null, ex);
        } else if (st.countTokens() < colLex) {
            Error ex = new Error("Numero de colunas errado no arquivo de configuração! esperado: " + colLex + " encontrado: " + st.countTokens());
            Logger.getLogger(AnLexicoFactory.class.getName()).log(Level.WARNING, null, ex);
            throw ex;
        }
        for (int j = 0; j < colLex; j++) {
            String s = st.nextToken();

            if (s.equals("x")) {
                transTableLex[j][i] = -1;
            } else {
                transTableLex[j][i] = Integer.parseInt(s);
            }
        }
    }

    /**
     * Extract size of the transition table
     *
     * @param DataInputStream Léxico Configuration file
     * @return StringTokenizer
     */
    private StringTokenizer getSizeOfTransitionTableLex(DataInputStream dis) throws NumberFormatException, IOException {
        @SuppressWarnings("deprecation")
        StringTokenizer st = new StringTokenizer(dis.readLine());
        String colluns = st.nextToken();
        String lines = st.nextToken();

        colLex = Integer.parseInt(colluns);
        linLex = Integer.parseInt(lines);

        return st;
    }
}
